Skip to content

Display custom emoji icons and styles on inline and reply keyboard buttons (Bot API 9.4) - #1091

Open
sehamilton0805 wants to merge 5 commits into
TGX-Android:mainfrom
sehamilton0805:button-styles-and-icons
Open

Display custom emoji icons and styles on inline and reply keyboard buttons (Bot API 9.4)#1091
sehamilton0805 wants to merge 5 commits into
TGX-Android:mainfrom
sehamilton0805:button-styles-and-icons

Conversation

@sehamilton0805

Copy link
Copy Markdown

Bot API 9.4 (February 2026) added two per-button fields that official clients already render, while Telegram X currently ignores them: iconCustomEmojiId (a custom emoji shown before the button text) and style (primary / success / danger accent). The bundled TDLib already exposes both on TdApi.InlineKeyboardButton and TdApi.KeyboardButton, so this PR is rendering-only — no TDLib or layer changes.

What it does

Commit 1 — inline keyboards (TGInlineKeyboard, TGMessage, MessageView):

  • iconCustomEmojiId is rendered before the button text via a single-emoji Text (the same FormattedText("*") + TextEntityTypeCustomEmoji pattern as EmojiStatusHelper), so animated/static emoji, thumbnails and premium repainting all reuse the existing machinery. Media lives in a new dedicated lazily-created ComplexReceiver on MessageView (modeled after footerTextMediaReceiver), attached/detached/destroyed with the view; requests are wired through requestAllTextMedia, invalidation through performWithViews like hAuthorEmojiStatus. Text width is reduced by the icon footprint during measure; the icon+text group stays centered, RTL mirrored.
  • style maps onto the existing per-button color override path (customColorId) using the app's canonical semantic colors (OptionsLayout.getOptionColorId precedent): primary → textNeutral, success → iconPositive (same as the giveaway "participating" button), danger → textNegative. In plain mode it colors outline + text + ripple + progress + corner type icons; in bubble mode the text is tinted while the standard translucent fill is kept.
  • Drive-by fix: the stale-button trimming loop in buildLayout removed only every other extra button (remove(i) while iterating forward); replaced with a correct shrink that also releases per-button resources.

Commit 2 — reply (bot) keyboards (CommandKeyboardLayout, MessagesController):

  • Buttons are now CustomEmojiTextView (existing widget); the icon is a CustomEmojiId span over a placeholder char prepended to the label — EmojiFilter does the rest.
  • style drives the per-button text color, re-registered on every bind via addOrUpdateThemeTextColorListener since views are recycled between keyboards.
  • Lifecycle: recycled-away buttons and the layout itself get performDestroy(); the controller-level teardown happens only in the destroyInstance || !reuseEnabled branch (same pattern as emojiLayout), keeping the reuse-enabled cached controller intact.

Testing

  • Both commits apply on the latest main and :app:compileLatestUniversalDebugJavaWithJavac passes clean.
  • The change went through an additional multi-pass review focused on receiver lifecycle (keyboard removal via markup edit no longer leaves hidden animated emoji decoding), measurement edge cases (icon footprint can no longer push the text layout width ≤ 0 on many-column rows), style tint at rest for corner type icons, and controller reuse.
  • Screenshots: I don't have a local full (native) build environment, so before/after screenshots will follow from the first test build; happy to iterate on the visual details (icon size 17dp ≈ button text size, 4dp spacing, style colors) per your design feedback.

sehamilton0805 and others added 5 commits August 3, 2026 09:09
Renders InlineKeyboardButton.iconCustomEmojiId before the button text
and maps ButtonStyle primary/success/danger to textNeutral/iconPositive/
textNegative theme colors (Bot API 9.4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same Bot API 9.4 fields for reply keyboards: CommandKeyboardLayout
buttons become CustomEmojiTextView with a CustomEmojiId span for
iconCustomEmojiId, and ButtonStyle drives the per-button text color.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Icon width made tight labels wrap at arbitrary characters. Force
greedy break strategy without hyphenation and cap at 2 lines with
end ellipsis, matching inline keyboard behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The icon footprint narrows the EmojiString layout, so labels that
previously fit exactly were char-broken by StaticLayout. Shrink the
font (14..10dp) until the widest word fits the reduced width; measure
the default step at 15dp matching the actual getBoldPaint14 size.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auto-size cannot prevent mid-word breaks (a split-word layout still
satisfies its constraints); shrink the font until the widest word and
a 2-line label fit instead, refitting when keyboards are swapped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sehamilton0805

Copy link
Copy Markdown
Author

Before / after screenshots, taken with a bot that sets iconCustomEmojiId on its inline keyboard buttons.

Before (current main) — the icon field is ignored; note that long labels on narrow buttons already break mid-word today:

before

After (this PR) — custom emoji icons are rendered before the label, and the button font is fitted to the widest word, so icons never cause mid-word breaks (tight labels shrink slightly instead):

after

ButtonStyle colors are not shown here — happy to add screenshots of those (and of reply keyboards) if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant